using QRCoder;
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Parsing;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using static BBDown.Core.Entity.Entity;
using static BBDown.BBDownUtil;
using static BBDown.BBDownDownloadUtil;
using static BBDown.Core.Parser;
using static BBDown.Core.Logger;
using System.Linq;
using System.Text.Json;
using System.Text.RegularExpressions;
using BBDown.Core;
using BBDown.Core.Util;
using System.Text.Json.Serialization;
using System.CommandLine.Builder;
using BBDown.Core.Entity;

namespace BBDown
{
    partial class Program
    {
        private static readonly string BACKUP_HOST = "upos-sz-mirrorcoso1.bilivideo.com";
        public static string SinglePageDefaultSavePath { get; set; } = "<videoTitle>";
        public static string MultiPageDefaultSavePath { get; set; } = "<videoTitle>/[P<pageNumberWithZero>]<pageTitle>";

        public readonly static string APP_DIR = Path.GetDirectoryName(Environment.ProcessPath)!;

        private static int Compare(Audio r1, Audio r2)
        {
            return r1.bandwith - r2.bandwith > 0 ? -1 : 1;
        }

        private static string FormatTimeStamp(long ts, string format)
        {
            try
            {
                return ts == 0 ? "null" : DateTimeOffset.FromUnixTimeSeconds(ts).ToLocalTime().ToString(format);
            }
            catch (Exception ex)
            {
                LogError($"格式化日期出错: {ex.Message}");
                return ts.ToString();
            }
        }

        [JsonSerializable(typeof(MyOption))]
        partial class MyOptionJsonContext : JsonSerializerContext { }

        private static void Console_CancelKeyPress(object? sender, ConsoleCancelEventArgs e)
        {
            LogWarn("Force Exit...");
            try
            {
                Console.ResetColor();
                Console.CursorVisible = true;
                if (!OperatingSystem.IsWindows())
                    System.Diagnostics.Process.Start("stty", "echo");
            }
            catch { }
            Environment.Exit(0);
        }

        public static async Task<int> Main(params string[] args)
        {
            Console.CancelKeyPress += Console_CancelKeyPress;
            ServicePointManager.DefaultConnectionLimit = 2048;

            var rootCommand = CommandLineInvoker.GetRootCommand(RunApp);
            Command loginCommand = new(
                "login",
                "通过APP扫描二维码以登录您的WEB账号");
            rootCommand.AddCommand(loginCommand);
            Command loginTVCommand = new(
                "logintv",
                "通过APP扫描二维码以登录您的TV账号");
            rootCommand.AddCommand(loginTVCommand);
            var serverUrlOpt = new Option<string>(
                new[] { "--listen", "-l" },
                description: "服务器监听url");
            Command runAsServerCommand = new(
                "serve",
                "以服务器模式运行")
            { serverUrlOpt };
            runAsServerCommand.SetHandler(StartServer, serverUrlOpt);
            rootCommand.AddCommand(runAsServerCommand);
            rootCommand.Description = "BBDown是一个免费且便捷高效的哔哩哔哩下载/解析软件.";
            rootCommand.TreatUnmatchedTokensAsErrors = true;

            //WEB登录
            loginCommand.SetHandler(BBDownLoginUtil.LoginWEB);

            //TV登录
            loginTVCommand.SetHandler(BBDownLoginUtil.LoginTV);

            var parser = new CommandLineBuilder(rootCommand)
                .UseDefaults()
                .EnablePosixBundling(false)
                .UseExceptionHandler((ex, context) =>
                {
                    LogError(ex.Message);
                    try { Console.CursorVisible = true; } catch { }
                    Thread.Sleep(3000);
                    Environment.Exit(1);
                }, 1)
                .Build();

            var newArgsList = new List<string>();
            var commandLineResult = rootCommand.Parse(args);

            //显式抛出异常
            if (commandLineResult.Errors.Any())
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.Error.WriteLine(commandLineResult.Errors.First().Message);
                Console.ResetColor();
                Console.Error.WriteLine($"请使用 BBDown --help 查看帮助");
                return 1;
            }

            if (commandLineResult.CommandResult.Command.Name.ToLower() != Path.GetFileNameWithoutExtension(Environment.ProcessPath)!.ToLower())
            {
                // 服务器模式需要完整的arg列表
                if (commandLineResult.CommandResult.Command.Name.ToLower() == "serve")
                {
                    return await parser.InvokeAsync(args.ToArray());
                }
                newArgsList.Add(commandLineResult.CommandResult.Command.Name);
                return await parser.InvokeAsync(newArgsList.ToArray());
            }

            foreach (var item in commandLineResult.CommandResult.Children)
            {
                if (item is ArgumentResult a)
                {
                    newArgsList.Add(a.Tokens[0].Value);
                }
                else if (item is OptionResult o)
                {
                    newArgsList.Add("--" + o.Option.Name);
                    newArgsList.AddRange(o.Tokens.Select(t => t.Value));
                }
            }

            if (newArgsList.Contains("--debug"))
            {
                Config.DEBUG_LOG = true;
            }

            Console.BackgroundColor = ConsoleColor.DarkBlue;
            Console.ForegroundColor = ConsoleColor.White;
            var ver = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version!;
            Console.Write($"BBDown version {ver.Major}.{ver.Minor}.{ver.Build}, Bilibili Downloader.\r\n");
            Console.ResetColor();
            Console.Write("遇到问题请首先到以下地址查阅有无相关信息：\r\nhttps://github.com/nilaoda/BBDown/issues\r\n");
            Console.WriteLine();

            //处理配置文件
            BBDownConfigParser.HandleConfig(newArgsList, rootCommand);

            return await parser.InvokeAsync(newArgsList.ToArray());
        }

        private static Task RunApp(MyOption myOption)
        {
            //检测更新
            CheckUpdateAsync();
            return DoWorkAsync(myOption);
        }

        private static void StartServer(string? listenUrl)
        {
            var defaultListenUrl = "http://0.0.0.0:23333";
            //检测更新
            CheckUpdateAsync();
            var server = new BBDownApiServer();
            server.SetUpServer();
            server.Run(string.IsNullOrEmpty(listenUrl) ? defaultListenUrl : listenUrl);
        }

        public static (Dictionary<string, byte> encodingPriority, Dictionary<string, int> dfnPriority, string? firstEncoding,
            bool downloadDanmaku, string input, string savePathFormat, string lang, string aidOri, int delay)
        SetUpWork(MyOption myOption)
        {
            //处理废弃选项
            HandleDeprecatedOptions(myOption);

            //处理冲突选项
            HandleConflictingOptions(myOption);

            //寻找并设置所需的二进制文件路径
            FindBinaries(myOption);

            //切换工作目录
            ChangeWorkingDir(myOption);

            //解析优先级
            var encodingPriority = ParseEncodingPriority(myOption, out var firstEncoding);
            var dfnPriority = ParseDfnPriority(myOption);

            //优先使用用户设置的UA
            HTTPUtil.UserAgent = string.IsNullOrEmpty(myOption.UserAgent) ? HTTPUtil.UserAgent : myOption.UserAgent;

            bool downloadDanmaku = myOption.DownloadDanmaku || myOption.DanmakuOnly;
            string input = myOption.Url;
            string savePathFormat = myOption.FilePattern;
            string lang = myOption.Language;
            string aidOri = ""; //原始aid
            int delay = Convert.ToInt32(myOption.DelayPerPage);
            Config.DEBUG_LOG = myOption.Debug;
            Config.HOST = myOption.Host;
            Config.EPHOST = myOption.EpHost;
            Config.AREA = myOption.Area;
            Config.COOKIE = myOption.Cookie;
            Config.TOKEN = myOption.AccessToken.Replace("access_token=", "");

            LogDebug("AppDirectory: {0}", APP_DIR);
            LogDebug("运行参数：{0}", JsonSerializer.Serialize(myOption, MyOptionJsonContext.Default.MyOption));
            return (encodingPriority, dfnPriority, firstEncoding, downloadDanmaku, input, savePathFormat, lang, aidOri, delay);
        }

        public static async Task<(string fetchedAid, VInfo vInfo, string apiType)> GetVideoInfoAsync(MyOption myOption, string aidOri, string input)
        {
            //加载认证信息
            LoadCredentials(myOption);

            // 检测是否登录了账号
            bool is_login = await CheckLogin(Config.COOKIE);
            if (!myOption.UseIntlApi && !myOption.UseTvApi && Config.AREA == "")
            {
                Log("检测账号登录...");
                if (!is_login)
                {
                    LogWarn("你尚未登录B站账号, 解析可能受到限制");
                }
            }

            Log("获取aid...");
            aidOri = await GetAvIdAsync(input);
            Log("获取aid结束: " + aidOri);

            if (string.IsNullOrEmpty(aidOri))
            {
                throw new Exception("输入有误");
            }

            Log("获取视频信息...");
            IFetcher fetcher = FetcherFactory.CreateFetcher(aidOri, myOption.UseIntlApi);
            var vInfo = await fetcher.FetchAsync(aidOri);
            string title = vInfo.Title;
            long pubTime = vInfo.PubTime;
            LogColor("视频标题: " + title);
            if (pubTime != 0)
            {
                Log("发布时间: " + FormatTimeStamp(pubTime, "yyyy-MM-dd HH:mm:ss zzz"));
            }
            var mid = vInfo.PagesInfo.FirstOrDefault(p => !string.IsNullOrEmpty(p.ownerMid))?.ownerMid;
            if (!string.IsNullOrEmpty(mid))
            {
                Log($"UP主页: https://space.bilibili.com/{mid}");
            }
            string apiType = myOption.UseTvApi ? "TV" : (myOption.UseAppApi ? "APP" : (myOption.UseIntlApi ? "INTL" : "WEB"));

            //打印分P信息
            List<Page> pagesInfo = vInfo.PagesInfo;
            bool more = false;
            foreach (Page p in pagesInfo)
            {
                if (!myOption.ShowAll)
                {
                    if (more && p.index != pagesInfo.Count) continue;
                    if (!more && p.index > 5)
                    {
                        Log("......");
                        more = true;
                        continue;
                    }
                }

                Log($"P{p.index}: [{p.cid}] [{p.title}] [{FormatTime(p.dur)}]");
            }
            return (aidOri, vInfo, apiType);
        }

        public static async Task DownloadPagesAsync(MyOption myOption, VInfo vInfo, Dictionary<string, byte> encodingPriority, Dictionary<string, int> dfnPriority,
            string? firstEncoding, bool downloadDanmaku, string input, string savePathFormat, string lang, string aidOri, int delay, string apiType, DownloadTask? relatedTask = null)
        {
            List<Page> pagesInfo = vInfo.PagesInfo;
            bool bangumi = vInfo.IsBangumi;
            bool cheese = vInfo.IsCheese;
            //获取已选择的分P列表
            List<string>? selectedPages = GetSelectedPages(myOption, vInfo, input);

            Log($"共计 {pagesInfo.Count} 个分P, 已选择：" + (selectedPages == null ? "ALL" : string.Join(",", selectedPages)));
            var pagesCount = pagesInfo.Count;

            //过滤不需要的分P
            if (selectedPages != null)
            {
                pagesInfo = pagesInfo.Where(p => selectedPages.Contains(p.index.ToString())).ToList();
            }

            // 根据p数选择存储路径
            savePathFormat = string.IsNullOrEmpty(myOption.FilePattern) ? SinglePageDefaultSavePath : myOption.FilePattern;
            // 1. 多P; 2. 只有1P, 但是是番剧, 尚未完结时 按照多P处理
            if (pagesCount > 1 || (bangumi && !vInfo.IsBangumiEnd))
            {
                savePathFormat = string.IsNullOrEmpty(myOption.MultiFilePattern) ? MultiPageDefaultSavePath : myOption.MultiFilePattern;
            }

            foreach (Page p in pagesInfo)
            {
                if (pagesInfo.Count > 1 && delay > 0)
                {
                    Log($"停顿{delay}秒...");
                    await Task.Delay(delay * 1000);
                }
                Log($"开始解析P{p.index}: {p.aid}... ({pagesInfo.IndexOf(p) + 1} of {pagesInfo.Count})");

                if (myOption.SaveArchivesToFile)
                {
                    if (CheckAidFromFile(p.aid))
                    {

                        Log($"aid: {p.aid}已下载过, 跳过下载...");
                        continue;
                    }
                }

                await DownloadPageAsync(p, myOption, vInfo, encodingPriority, dfnPriority, firstEncoding,
                    downloadDanmaku, input, savePathFormat, lang, aidOri, apiType, relatedTask);

                if (myOption.SaveArchivesToFile)
                {
                    SaveAidToFile(p.aid);
                }
            }

            Log("任务完成");
        }

        private static async Task DownloadPageAsync(Page p, MyOption myOption, VInfo vInfo, Dictionary<string, byte> encodingPriority, Dictionary<string, int> dfnPriority,
            string? firstEncoding, bool downloadDanmaku, string input, string savePathFormat, string lang, string aidOri, string apiType, DownloadTask? relatedTask = null)
        {
            List<Page> pagesInfo = vInfo.PagesInfo;
            string desc = string.IsNullOrEmpty(p.desc) ? vInfo.Desc : p.desc;
            bool bangumi = vInfo.IsBangumi;
            var pagesCount = pagesInfo.Count;
            List<Subtitle> subtitleInfo = new();
            string title = vInfo.Title;
            string pic = vInfo.Pic;
            long pubTime = vInfo.PubTime;
            bool selected = false; //用户是否已经手动选择过了轨道
            int retryCount = 0;
        downloadPage:
            try
            {
                LogDebug("尝试获取章节信息...");
                p.points = await FetchPointsAsync(p.cid, p.aid);

                string videoPath = $"{p.aid}/{p.aid}.P{p.index}.{p.cid}.mp4";
                string audioPath = $"{p.aid}/{p.aid}.P{p.index}.{p.cid}.m4a";
                var coverPath = $"{p.aid}/{p.aid}.jpg";

                //处理文件夹以.结尾导致的异常情况
                if (title.EndsWith(".")) title += "_fix";
                //处理文件夹以.开头导致的异常情况
                if (title.StartsWith(".")) title = "_" + title;

                //处理封面&&字幕
                if (!myOption.OnlyShowInfo)
                {
                    if (!Directory.Exists(p.aid))
                    {
                        Directory.CreateDirectory(p.aid);
                    }
                    if (!myOption.SkipCover && !myOption.SubOnly && !File.Exists(coverPath) && !myOption.DanmakuOnly && !myOption.CoverOnly)
                    {
                        await DownloadFile((pic == "" ? p.cover! : pic), coverPath, new DownloadConfig());
                    }

                    if (!myOption.SkipSubtitle && !myOption.DanmakuOnly && !myOption.CoverOnly)
                    {
                        LogDebug("获取字幕...");
                        subtitleInfo = await SubUtil.GetSubtitlesAsync(p.aid, p.cid, p.epid, p.index, myOption.UseIntlApi);
                        if (myOption.SkipAi && subtitleInfo.Any())
                        {
                            Log($"跳过下载AI字幕");
                            subtitleInfo = subtitleInfo.Where(s => !s.lan.StartsWith("ai-")).ToList();
                        }
                        foreach (Subtitle s in subtitleInfo)
                        {
                            Log($"下载字幕 {s.lan} => {SubUtil.GetSubtitleCode(s.lan).Item2}...");
                            LogDebug("下载：{0}", s.url);
                            await SubUtil.SaveSubtitleAsync(s.url, s.path);
                            if (myOption.SubOnly && File.Exists(s.path) && File.ReadAllText(s.path) != "")
                            {
                                var _outSubPath = FormatSavePath(savePathFormat, title, null, null, p, pagesCount, apiType, pubTime);
                                if (_outSubPath.Contains('/'))
                                {
                                    if (!Directory.Exists(_outSubPath.Split('/').First()))
                                        Directory.CreateDirectory(_outSubPath.Split('/').First());
                                }
                                _outSubPath = Path.ChangeExtension(_outSubPath, $".{s.lan}.srt");
                                File.Move(s.path, _outSubPath, true);
                            }
                        }
                    }

                    if (myOption.SubOnly)
                    {
                        if (Directory.Exists(p.aid) && Directory.GetFiles(p.aid).Length == 0) Directory.Delete(p.aid, true);
                        return;
                    }
                }

                //调用解析
                ParsedResult parsedResult = await ExtractTracksAsync(aidOri, p.aid, p.cid, p.epid, myOption.UseTvApi, myOption.UseIntlApi, myOption.UseAppApi, firstEncoding);
                List<AudioMaterial> audioMaterial = new();
                if (!p.points.Any())
                {
                    p.points = parsedResult.ExtraPoints;
                }

                if (Config.DEBUG_LOG)
                {
                    File.WriteAllText($"debug_{DateTime.Now:yyyyMMddHHmmssfff}.json", parsedResult.WebJsonString);
                }

                var savePath = "";

                var downloadConfig = new DownloadConfig()
                {
                    UseAria2c = myOption.UseAria2c,
                    Aria2cArgs = myOption.Aria2cArgs,
                    ForceHttp = myOption.ForceHttp,
                    MultiThread = myOption.MultiThread,
                    RelatedTask = relatedTask,
                };

                //此处代码简直灾难, 后续优化吧
                if ((parsedResult.VideoTracks.Any() || parsedResult.AudioTracks.Any()) && !parsedResult.Clips.Any())   //dash
                {
                    if (parsedResult.VideoTracks.Count == 0)
                    {
                        LogError("没有找到符合要求的视频流");
                        if (!myOption.AudioOnly) return;
                    }
                    if (parsedResult.AudioTracks.Count == 0)
                    {
                        LogError("没有找到符合要求的音频流");
                        if (!myOption.VideoOnly) return;
                    }

                    if (myOption.AudioOnly)
                    {
                        parsedResult.VideoTracks.Clear();
                    }
                    if (myOption.VideoOnly)
                    {
                        parsedResult.AudioTracks.Clear();
                        parsedResult.BackgroundAudioTracks.Clear();
                        parsedResult.RoleAudioList.Clear();
                    }

                    //排序
                    parsedResult.VideoTracks = SortTracks(parsedResult.VideoTracks, dfnPriority, encodingPriority, myOption.VideoAscending);
                    parsedResult.AudioTracks.Sort(Compare);
                    parsedResult.BackgroundAudioTracks.Sort(Compare);
                    foreach (var role in parsedResult.RoleAudioList)
                    {
                        role.audio.Sort(Compare);
                    }
                    if (myOption.AudioAscending)
                    {
                        parsedResult.AudioTracks.Reverse();
                        parsedResult.BackgroundAudioTracks.Reverse();
                        foreach (var role in parsedResult.RoleAudioList)
                        {
                            role.audio.Reverse();
                        }
                    }

                    //打印轨道信息
                    if (!myOption.HideStreams)
                    {
                        PrintAllTracksInfo(parsedResult, p.dur, myOption.OnlyShowInfo);
                    }

                    //仅展示 跳过下载
                    if (myOption.OnlyShowInfo)
                    {
                        return;
                    }

                    int vIndex = 0; //用户手动选择的视频序号
                    int aIndex = 0; //用户手动选择的音频序号

                    //选择轨道
                    if (myOption.Interactive && !selected)
                    {
                        SelectTrackManually(parsedResult, ref vIndex, ref aIndex);
                        selected = true;
                    }

                    Video? selectedVideo = parsedResult.VideoTracks.ElementAtOrDefault(vIndex);
                    Audio? selectedAudio = parsedResult.AudioTracks.ElementAtOrDefault(aIndex);
                    Audio? selectedBackgroundAudio = parsedResult.BackgroundAudioTracks.ElementAtOrDefault(aIndex);

                    LogDebug("Format Before: " + savePathFormat);
                    savePath = FormatSavePath(savePathFormat, title, selectedVideo, selectedAudio, p, pagesCount, apiType, pubTime);
                    LogDebug("Format After: " + savePath);

                    if (downloadDanmaku)
                    {
                        var danmakuXmlPath = Path.ChangeExtension(savePath, ".xml");
                        var danmakuAssPath = Path.ChangeExtension(savePath, ".ass");
                        Log("正在下载弹幕Xml文件");
                        string danmakuUrl = $"https://comment.bilibili.com/{p.cid}.xml";
                        await DownloadFile(danmakuUrl, danmakuXmlPath, downloadConfig);
                        var danmakus = DanmakuUtil.ParseXml(danmakuXmlPath);
                        if (danmakus != null)
                        {
                            Log("正在保存弹幕Ass文件...");
                            await DanmakuUtil.SaveAsAssAsync(danmakus, danmakuAssPath);
                        }
                        else
                        {
                            Log("弹幕Xml解析失败, 删除Xml...");
                            File.Delete(danmakuXmlPath);
                        }
                        if (myOption.DanmakuOnly)
                        {
                            if (Directory.Exists(p.aid))
                            {
                                Directory.Delete(p.aid);
                            }
                            return;
                        }
                    }

                    if (myOption.CoverOnly)
                    {
                        var coverUrl = pic == "" ? p.cover! : pic;
                        var newCoverPath = Path.ChangeExtension(savePath, Path.GetExtension(coverUrl));
                        await DownloadFile(coverUrl, newCoverPath, downloadConfig);
                        if (Directory.Exists(p.aid) && Directory.GetFiles(p.aid).Length == 0) Directory.Delete(p.aid, true);
                        return;
                    }

                    Log($"已选择的流:");
                    PrintSelectedTrackInfo(selectedVideo, selectedAudio, p.dur);

                    //用户开启了强制替换
                    if (myOption.ForceReplaceHost && string.IsNullOrEmpty(myOption.UposHost))
                    {
                        myOption.UposHost = BACKUP_HOST;
                    }

                    //处理PCDN
                    HandlePcdn(myOption, selectedVideo, selectedAudio);

                    if (!myOption.OnlyShowInfo && File.Exists(savePath) && new FileInfo(savePath).Length != 0)
                    {
                        Log($"{savePath}已存在, 跳过下载...");
                        File.Delete(coverPath);
                        if (Directory.Exists(p.aid) && Directory.GetFiles(p.aid).Length == 0)
                        {
                            Directory.Delete(p.aid, true);
                        }
                        return;
                    }

                    if (selectedVideo != null)
                    {
                        //杜比视界, 若ffmpeg版本小于5.0, 使用mp4box封装
                        if (selectedVideo.dfn == Config.qualitys["126"] && !myOption.UseMP4box && !CheckFFmpegDOVI())
                        {
                            LogWarn($"检测到杜比视界清晰度且您的ffmpeg版本小于5.0,将使用mp4box混流...");
                            myOption.UseMP4box = true;
                        }
                        Log($"开始下载P{p.index}视频...");
                        await DownloadTrackAsync(selectedVideo.baseUrl, videoPath, downloadConfig, video: true);
                    }

                    if (selectedAudio != null)
                    {
                        Log($"开始下载P{p.index}音频...");
                        await DownloadTrackAsync(selectedAudio.baseUrl, audioPath, downloadConfig, video: false);
                    }

                    if (selectedBackgroundAudio != null)
                    {
                        var backgroundPath = $"{p.aid}/{p.aid}.{p.cid}.P{p.index}.back_ground.m4a";
                        Log($"开始下载P{p.index}背景配音...");
                        await DownloadTrackAsync(selectedBackgroundAudio.baseUrl, backgroundPath, downloadConfig, video: false);
                        audioMaterial.Add(new AudioMaterial("背景音频", "", backgroundPath));
                    }

                    if (parsedResult.RoleAudioList.Any())
                    {
                        foreach (var role in parsedResult.RoleAudioList)
                        {
                            Log($"开始下载P{p.index}配音[{role.title}]...");
                            await DownloadTrackAsync(role.audio[aIndex].baseUrl, role.path, downloadConfig, video: false);
                            audioMaterial.Add(new AudioMaterial(role));
                        }
                    }

                    Log($"下载P{p.index}完毕");
                    if (!parsedResult.VideoTracks.Any()) videoPath = "";
                    if (!parsedResult.AudioTracks.Any()) audioPath = "";
                    if (myOption.SkipMux) return;
                    Log($"开始合并音视频{(subtitleInfo.Any() ? "和字幕" : "")}...");
                    if (myOption.AudioOnly)
                        savePath = savePath[..^4] + ".m4a";
                    int code = BBDownMuxer.MuxAV(myOption.UseMP4box, videoPath, audioPath, audioMaterial, savePath,
                        desc,
                        title,
                        p.ownerName ?? "",
                        (pagesCount > 1 || (bangumi && !vInfo.IsBangumiEnd)) ? p.title : "",
                        File.Exists(coverPath) ? coverPath : "",
                        lang,
                        subtitleInfo, myOption.AudioOnly, myOption.VideoOnly, p.points, p.pubTime, myOption.SimplyMux);
                    if (code != 0 || !File.Exists(savePath) || new FileInfo(savePath).Length == 0)
                    {
                        LogError("合并失败"); return;
                    }
                    Log("清理临时文件...");
                    Thread.Sleep(200);
                    if (parsedResult.VideoTracks.Any()) File.Delete(videoPath);
                    if (parsedResult.AudioTracks.Any()) File.Delete(audioPath);
                    if (p.points.Any()) File.Delete(Path.Combine(Path.GetDirectoryName(string.IsNullOrEmpty(videoPath) ? audioPath : videoPath)!, "chapters"));
                    foreach (var s in subtitleInfo) File.Delete(s.path);
                    foreach (var a in audioMaterial) File.Delete(a.path);
                    if (pagesInfo.Count == 1 || p.index == pagesInfo.Last().index || p.aid != pagesInfo.Last().aid)
                        File.Delete(coverPath);
                    if (Directory.Exists(p.aid) && Directory.GetFiles(p.aid).Length == 0) Directory.Delete(p.aid, true);
                }
                else if (parsedResult.Clips.Any() && parsedResult.Dfns.Any())   //flv
                {
                    bool flag = false;
                    var clips = parsedResult.Clips;
                    var dfns = parsedResult.Dfns;
                reParse:
                    //排序
                    parsedResult.VideoTracks = SortTracks(parsedResult.VideoTracks, dfnPriority, encodingPriority, myOption.VideoAscending);

                    int vIndex = 0;
                    if (myOption.Interactive && !flag && !selected)
                    {
                        int i = 0;
                        dfns.ForEach(key => LogColor($"{i++}.{Config.qualitys[key]}"));
                        Log("请选择最想要的清晰度(输入序号): ", false);
                        Console.ForegroundColor = ConsoleColor.Cyan;
                        vIndex = Convert.ToInt32(Console.ReadLine());
                        if (vIndex > dfns.Count || vIndex < 0) vIndex = 0;
                        Console.ResetColor();
                        //重新解析
                        parsedResult.VideoTracks.Clear();
                        parsedResult = await ExtractTracksAsync(aidOri, p.aid, p.cid, p.epid, myOption.UseTvApi, myOption.UseIntlApi, myOption.UseAppApi, firstEncoding, dfns[vIndex]);
                        if (!p.points.Any()) p.points = parsedResult.ExtraPoints;
                        flag = true;
                        selected = true;
                        goto reParse;
                    }

                    Log($"共计{parsedResult.VideoTracks.Count}条流(共有{clips.Count}个分段).");
                    int index = 0;
                    foreach (var v in parsedResult.VideoTracks)
                    {
                        LogColor($"{index++}. [{v.dfn}] [{v.res}] [{v.codecs}] [{v.fps}] [~{v.size / 1024 / v.dur * 8:00} kbps] [{FormatFileSize(v.size)}]".Replace("[] ", ""), false);
                        if (myOption.OnlyShowInfo)
                        {
                            clips.ForEach(Console.WriteLine);
                        }
                    }
                    if (myOption.OnlyShowInfo) return;
                    savePath = FormatSavePath(savePathFormat, title, parsedResult.VideoTracks.ElementAtOrDefault(vIndex), null, p, pagesCount, apiType, pubTime);
                    if (File.Exists(savePath) && new FileInfo(savePath).Length != 0)
                    {
                        Log($"{savePath}已存在, 跳过下载...");
                        if (pagesInfo.Count == 1 && Directory.Exists(p.aid))
                        {
                            Directory.Delete(p.aid, true);
                        }
                        return;
                    }
                    var pad = string.Empty.PadRight(clips.Count.ToString().Length, '0');
                    for (int i = 0; i < clips.Count; i++)
                    {
                        var link = clips[i];
                        videoPath = $"{p.aid}/{p.aid}.P{p.index}.{p.cid}.{i.ToString(pad)}.mp4";
                        Log($"开始下载P{p.index}视频, 片段({(i + 1).ToString(pad)}/{clips.Count})...");
                        await DownloadTrackAsync(link, videoPath, downloadConfig, video: true);
                    }
                    Log($"下载P{p.index}完毕");
                    Log("开始合并分段...");
                    var files = GetFiles(Path.GetDirectoryName(videoPath)!, ".mp4");
                    videoPath = $"{p.aid}/{p.aid}.P{p.index}.{p.cid}.mp4";
                    BBDownMuxer.MergeFLV(files, videoPath);
                    if (myOption.SkipMux) return;
                    Log($"开始混流视频{(subtitleInfo.Any() ? "和字幕" : "")}...");
                    if (myOption.AudioOnly)
                        savePath = savePath[..^4] + ".m4a";
                    int code = BBDownMuxer.MuxAV(false, videoPath, "", audioMaterial, savePath,
                        desc,
                        title,
                        p.ownerName ?? "",
                        (pagesCount > 1 || (bangumi && !vInfo.IsBangumiEnd)) ? p.title : "",
                        File.Exists(coverPath) ? coverPath : "",
                        lang,
                        subtitleInfo, myOption.AudioOnly, myOption.VideoOnly, p.points, p.pubTime, myOption.SimplyMux);
                    if (code != 0 || !File.Exists(savePath) || new FileInfo(savePath).Length == 0)
                    {
                        LogError("合并失败"); return;
                    }
                    Log("清理临时文件...");
                    Thread.Sleep(200);
                    if (parsedResult.VideoTracks.Count != 0) File.Delete(videoPath);
                    foreach (var s in subtitleInfo) File.Delete(s.path);
                    foreach (var a in audioMaterial) File.Delete(a.path);
                    if (p.points.Any()) File.Delete(Path.Combine(Path.GetDirectoryName(string.IsNullOrEmpty(videoPath) ? audioPath : videoPath)!, "chapters"));
                    if (pagesInfo.Count == 1 || p.index == pagesInfo.Last().index || p.aid != pagesInfo.Last().aid)
                        File.Delete(coverPath);
                    if (Directory.Exists(p.aid) && Directory.GetFiles(p.aid).Length == 0) Directory.Delete(p.aid, true);
                }
                else
                {
                    LogError("解析此分P失败(建议--debug查看详细信息)");
                    if (parsedResult.WebJsonString.Length < 100)
                    {
                        LogError(parsedResult.WebJsonString);
                    }
                    LogDebug("{0}", parsedResult.WebJsonString);
                    return;
                }
            }
            catch (Exception ex)
            {
                if (++retryCount > 2) throw;
                LogError(ex.Message);
                LogWarn("下载出现异常, 3秒后将进行自动重试...");
                await Task.Delay(3000);
                goto downloadPage;
            }
        }

        private static async Task DoWorkAsync(MyOption myOption)
        {
            try
            {
                var (encodingPriority, dfnPriority, firstEncoding, downloadDanmaku,
                    input, savePathFormat, lang, aidOri, delay) = SetUpWork(myOption);
                var (fetchedAid, vInfo, apiType) = await GetVideoInfoAsync(myOption, aidOri, input);
                await DownloadPagesAsync(myOption, vInfo, encodingPriority, dfnPriority, firstEncoding, downloadDanmaku,
                    input, savePathFormat, lang, fetchedAid, delay, apiType);
            }
            catch (Exception e)
            {
                Console.BackgroundColor = ConsoleColor.Red;
                Console.ForegroundColor = ConsoleColor.White;
                var msg = Config.DEBUG_LOG ? e.ToString() : e.Message;
                Console.Write($"{msg}{Environment.NewLine}请尝试升级到最新版本后重试!");
                Console.ResetColor();
                Console.WriteLine();
                Thread.Sleep(1);
                Environment.Exit(1);
            }
        }

        private static List<Video> SortTracks(List<Video> videoTracks, Dictionary<string, int> dfnPriority, Dictionary<string, byte> encodingPriority, bool videoAscending)
        {
            //用户同时输入了自定义分辨率优先级和自定义编码优先级, 则根据输入顺序依次进行排序
            return dfnPriority.Any() && encodingPriority.Any() && Environment.CommandLine.IndexOf("--encoding-priority") < Environment.CommandLine.IndexOf("--dfn-priority")
                ? videoTracks
                    .OrderBy(v => encodingPriority.TryGetValue(v.codecs, out byte i) ? i : 100)
                    .ThenBy(v => dfnPriority.TryGetValue(v.dfn, out int i) ? i : 100)
                    .ThenByDescending(v => Convert.ToInt32(v.id))
                    .ThenBy(v => videoAscending ? v.bandwith : -v.bandwith)
                    .ToList()
                : videoTracks
                    .OrderBy(v => dfnPriority.TryGetValue(v.dfn, out int i) ? i : 100)
                    .ThenBy(v => encodingPriority.TryGetValue(v.codecs, out byte i) ? i : 100)
                    .ThenByDescending(v => Convert.ToInt32(v.id))
                    .ThenBy(v => videoAscending ? v.bandwith : -v.bandwith)
                    .ToList();
        }

        private static string FormatSavePath(string savePathFormat, string title, Video? videoTrack, Audio? audioTrack, Page p, int pagesCount, string apiType, long pubTime)
        {
            var result = savePathFormat.Replace('\\', '/');
            var regex = InfoRegex();
            foreach (Match m in regex.Matches(result).Cast<Match>())
            {
                var key = m.Groups[1].Value;

                //解析自定义日期格式
                var defaultDateFormat = "yyyy-MM-dd_HH-mm-ss";
                string[] prefixes = { "publishDate:", "videoDate:" };
                foreach (var prefix in prefixes)
                {
                    if (key.StartsWith(prefix))
                    {
                        defaultDateFormat = key.Substring(key.IndexOf(':') + 1);
                        key = prefix.Replace(":", "");
                        break;
                    }
                }

                var v = key switch
                {
                    "videoTitle" => GetValidFileName(title, filterSlash: true).Trim().TrimEnd('.').Trim(),
                    "pageNumber" => p.index.ToString(),
                    "pageNumberWithZero" => p.index.ToString().PadLeft(pagesCount.ToString().Length, '0'),
                    "pageTitle" => GetValidFileName(p.title, filterSlash: true).Trim().TrimEnd('.').Trim(),
                    "bvid" => p.bvid,
                    "aid" => p.aid,
                    "cid" => p.cid,
                    "ownerName" => p.ownerName == null ? "" : GetValidFileName(p.ownerName, filterSlash: true).Trim().TrimEnd('.').Trim(),
                    "ownerMid" => p.ownerMid ?? "",
                    "dfn" => videoTrack == null ? "" : videoTrack.dfn,
                    "res" => videoTrack == null ? "" : videoTrack.res,
                    "fps" => videoTrack == null ? "" : videoTrack.fps,
                    "videoCodecs" => videoTrack == null ? "" : videoTrack.codecs,
                    "videoBandwidth" => videoTrack == null ? "" : videoTrack.bandwith.ToString(),
                    "audioCodecs" => audioTrack == null ? "" : audioTrack.codecs,
                    "audioBandwidth" => audioTrack == null ? "" : audioTrack.bandwith.ToString(),
                    "publishDate" => FormatTimeStamp(pubTime, defaultDateFormat),
                    "videoDate" => FormatTimeStamp(p.pubTime, defaultDateFormat),
                    "apiType" => apiType,
                    _ => $"<{key}>"
                };
                result = result.Replace(m.Value, v);
            }
            if (!result.EndsWith(".mp4")) { result += ".mp4"; }
            return result;
        }

        [GeneratedRegex("<([\\w:]+?)>")]
        private static partial Regex InfoRegex();
    }
}
